﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CollectionComparer.cs" company="" />
// <summary>
//   A readonly comparer for two collections of comparable items.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Utilities
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;

    /// <summary>
    /// A readonly comparer for two collections of comparable items.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in the compared collections.</typeparam>
    public class CollectionComparer<TKey, TElement> : ICollectionComparer<TKey, TElement>
        where TKey : IEquatable<TKey>
        where TElement : IEquatable<TElement>
    {
        /// <summary>
        /// Maps the keys identifying elements only in the original collection to their elements.
        /// </summary>
        private IReadOnlyDictionary<TKey, TElement> m_onlyOriginal;

        /// <summary>
        /// Maps the keys identifying elements only in the target collection to their elements.
        /// </summary>
        private IReadOnlyDictionary<TKey, TElement> m_onlyTarget;

        /// <summary>
        /// Maintains the set of keys of similar elements in the colletion.
        /// </summary>
        private readonly IReadOnlyDictionary<TKey, Tuple<TElement, TElement>> m_differences;

        /// <summary>
        /// Initializes a new instance of <see cref="CollectionComparer{TElement}"/> class with two given enumerables.
        /// </summary>
        /// <param name="fromCollection">The original collection (left hand side)</param>
        /// <param name="toCollection">The target collection (right hand side)</param>
        public CollectionComparer(IReadOnlyDictionary<TKey, TElement> fromCollection, IReadOnlyDictionary<TKey, TElement> toCollection)
        {
            var onlyOriginal = fromCollection
                .Where(original => !toCollection.ContainsKey(original.Key))
                .ToDictionary(original => original.Key, original => original.Value);

            m_onlyOriginal = new ReadOnlyDictionary<TKey, TElement>(onlyOriginal);

            var onlyTarget = toCollection
                .Where(target => !fromCollection.ContainsKey(target.Key))
                .ToDictionary(target => target.Key, target => target.Value);

            m_onlyTarget = new ReadOnlyDictionary<TKey, TElement>(onlyTarget);

            m_differences = fromCollection
                .Where(item => toCollection.ContainsKey(item.Key))
                .Where(item => !fromCollection[item.Key].Equals(toCollection[item.Key]))
                .ToDictionary(item => item.Key, item => new Tuple<TElement, TElement>(fromCollection[item.Key], toCollection[item.Key]));
        }

        #region Implementation of ICollectionComparer<TKey, TElement>

        public IReadOnlyDictionary<TKey, TElement> OnlyOriginal
        {
            get
            {
                return m_onlyOriginal;
            }
        }

        public IReadOnlyDictionary<TKey, TElement> OnlyTarget
        {
            get
            {
                return m_onlyTarget;
            }
        }

        public IReadOnlyDictionary<TKey, Tuple<TElement, TElement>> Differences
        {
            get
            {
                return m_differences;
            }
        }

        #endregion

        /// <summary>
        /// Switch the side of the original with the side of the target.
        /// NOTE: Does not switch the differences collection sides.
        /// </summary>
        public void SwitchSides()
        {
            var lastOriginal = m_onlyOriginal;

            m_onlyOriginal = m_onlyTarget;
            m_onlyTarget = lastOriginal;
        }
    }
}
